package il.technion.cs236369.dal;

import il.technion.cs236369.exceptions.NotFoundException;
import il.technion.cs236369.exceptions.NotValidException;
import il.technion.cs236369.interfaces.ICacheEntryDal;
import il.technion.cs236369.interfaces.ICacheManager;
import il.technion.cs236369.interfaces.IHttpCommunicator;

import java.util.Date;
import java.util.Properties;

import junit.framework.Assert;

import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.message.BasicHttpResponse;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class CacheManagerTests {

	private static ICacheManager		manager;
	private static CommStub				commStub	= new CommStub();
	private static HttpResponse			response;
	private static final String			URL			= "http://www.www.com";
	private static final HttpRequest	request		= new BasicHttpRequest("GET", URL);

	// contains and clear are tested throughout

	private static Properties getDefaultProperties() {
		Properties defaultProps = new Properties();

		defaultProps.setProperty("httproxy.db.driver", "com.mysql.jdbc.Driver");
		defaultProps.setProperty("httproxy.db.url", "jdbc:mysql://127.0.0.1:3306/");
		defaultProps.setProperty("httproxy.db.name", "proxy");
		defaultProps.setProperty("httproxy.db.table", "cache");
		defaultProps.setProperty("httproxy.db.username", "root");
		defaultProps.setProperty("httproxy.db.password", "1234");
		defaultProps.setProperty("httproxy.net.port", "3306");

		return defaultProps;
	}

	// a communication stub that returns diffrent responses based on a mode
	private static class CommStub implements IHttpCommunicator {

		public enum AcceptanceMode {
			NOT_MODIFIED, NEEDS_REFRESH, INVALID
		};

		private AcceptanceMode	_mode;

		public void setAcceptanceMode(AcceptanceMode mode) {
			_mode = mode;
		}

		@Override
		public HttpResponse get(HttpRequest getRequest) {
			HttpResponse newResponse = new BasicHttpResponse(HttpVersion.HTTP_1_1, 0, null);
			newResponse.addHeader(CacheManager.FRESHNESS_CONTROL_HEADER, new Date().getTime() + "");
			newResponse.addHeader("Host", URL);
			newResponse.setEntity(new ByteArrayEntity("Body".getBytes()));
			switch (_mode) {
			case INVALID:
				newResponse.setStatusCode(500);
				break;
			case NEEDS_REFRESH:
				newResponse.setStatusCode(CacheManager.OK);
				break;
			case NOT_MODIFIED:
				newResponse.setStatusCode(CacheManager.UNCHANGED);
			}

			return newResponse;
		}

	}

	@BeforeClass
	public static void init() throws Exception {
		manager = new CacheMangerFactoryImpl().create(commStub, getDefaultProperties());
		request.addHeader("Host", URL);
	}

	@Before
	public void setUp() throws Exception {
		manager.clear();

		response = new BasicHttpResponse(HttpVersion.HTTP_1_1, CacheManager.OK, null);
		response.addHeader(CacheManager.FRESHNESS_CONTROL_HEADER, new Date().getTime() + "");
		response.addHeader("Host", "http://www.website.com");
		response.setEntity(new ByteArrayEntity("Body".getBytes()));

		commStub.setAcceptanceMode(CommStub.AcceptanceMode.NOT_MODIFIED);
	}

	@After
	public void tearDown() throws Exception {
		manager.clear();
	}

	@Test
	public void testStore() throws Exception {
		Assert.assertFalse("does not exist yet", manager.contains(request));
		// bad code
		response.setStatusCode(199);
		Assert.assertFalse("bad status code", manager.store(URL, response));
		response.setStatusCode(CacheManager.OK);
		// bad headers
		response.addHeader(CacheManager.CACHE_HEADER, CacheManager.NO_STORE_HEADER);
		Assert.assertFalse("No store header", manager.store(URL, response));
		response.removeHeaders(CacheManager.CACHE_HEADER);
		response.removeHeaders(CacheManager.FRESHNESS_CONTROL_HEADER);
		Assert.assertFalse("No last modified header", manager.store(URL, response));
		response.addHeader(CacheManager.FRESHNESS_CONTROL_HEADER, new Date().toString());
		StringBuilder sb = new StringBuilder("Http://");
		// too long of a URL
		for (int i = sb.length(); i <= ICacheEntryDal.MAX_ALLOWED_LENGTH; i++) {
			sb.append("a");
		}
		response.removeHeaders("Host");
		response.addHeader("Host", sb.toString());
		Assert.assertFalse("URL too long", manager.store(sb.toString(), response));
		response.removeHeaders("Host");
		response.addHeader("Host", "http://www.website.com");
		Assert.assertTrue("succesful insertion", manager.store(URL, response));
		Assert.assertTrue("contains", manager.contains(request));
		Assert.assertTrue("refresh insertion", manager.store(URL, response));
		Assert.assertTrue("contains", manager.contains(request));
	}

	@Test
	public void loadTest() throws Exception {
		String currentTime = response.getFirstHeader(CacheManager.FRESHNESS_CONTROL_HEADER).getValue();
		manager.store(URL, response);
		HttpResponse newResponse = manager.load(request);
		String newTime = newResponse.getFirstHeader(CacheManager.FRESHNESS_CONTROL_HEADER).getValue();
		Assert.assertEquals("same entry", newTime, currentTime);
	}

	@Test(expected = NotFoundException.class)
	public void loadFailsNotFound() throws Exception {
		manager.load(request);
	}

	@Test(expected = NotValidException.class)
	public void loadFailsNotValid() throws Exception {
		manager.store(URL, response);
		commStub.setAcceptanceMode(CommStub.AcceptanceMode.INVALID);
		manager.load(request);
	}

	@Test
	public void loadDeletesInvalid() throws Exception {
		manager.store(URL, response);
		commStub.setAcceptanceMode(CommStub.AcceptanceMode.INVALID);
		try {
			manager.load(request);
		} catch (NotValidException e) {
			// do nothing
		}

		Assert.assertFalse("invalid entry deleted", manager.contains(request));
	}

	@Test
	public void loadRefreshes() throws Exception {
		String currentTime = response.getFirstHeader(CacheManager.FRESHNESS_CONTROL_HEADER).getValue();
		manager.store(URL, response);
		commStub.setAcceptanceMode(CommStub.AcceptanceMode.NEEDS_REFRESH);
		HttpResponse newResponse = manager.load(request);
		String newTime = newResponse.getFirstHeader(CacheManager.FRESHNESS_CONTROL_HEADER).getValue();
		// make sure time has changed
		Assert.assertFalse("entry refreshed", newTime.equals(currentTime));
	}

}
